﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OpenPasteSpider.projectmodel;
using Renci.SshNet;

namespace OpenPasteSpider
{
    /// <summary>
    /// 管理所有的SSH链接和执行命令
    /// </summary>
    public class LinkItem : IDisposable
    {


        /// <summary>
        /// 最大可发起的链接数
        /// </summary>
        private int _clientMaxCount = 3;

        /// <summary>
        /// 地址
        /// </summary>
        private string sshaddress { get; set; } = "";

        /// <summary>
        /// 端口
        /// </summary>
        private int sshport { get; set; } = 22;

        /// <summary>
        /// 用户名
        /// </summary>
        private string sshuser { get; set; } = "";

        /// <summary>
        /// 登录密码
        /// </summary>
        private string sshpass { get; set; } = "";

        /// <summary>
        /// 登录模式
        /// </summary>
        private EnumLinkLoginModel loginmodel { get; set; } = EnumLinkLoginModel.Account;

        /// <summary>
        /// 证书路径
        /// </summary>
        private string certpath { get; set; } = "";

        /// <summary>
        /// 证书密码
        /// </summary>
        private string certpass { get; set; } = "";

        /// <summary>
        /// 证书文本
        /// </summary>
        private string certbody { get; set; } = "";


        private enum EnumLinkLoginModel
        {
            Account = 1,
            CertFile = 2,
            CertBody = 3
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="linuxid"></param>
        /// <param name="code"></param>
        /// <param name="msg"></param>
        public delegate void RunDelegateEvent(int linuxid, string code, string msg);
        /// <summary>
        /// 
        /// </summary>
        public event RunDelegateEvent OnEvented;

        /// <summary>
        /// 连接池
        /// </summary>
        private List<LinkSSHClient> list;

        /// <summary>
        /// 资源锁
        /// </summary>
        private SemaphoreSlim slime;

        /// <summary>
        /// 日志
        /// </summary>
        private ILogger<LinkHelper> _logger;

        /// <summary>
        /// linux.id
        /// </summary>
        private int LinuxId { get; set; } = 0;

        /// <summary>
        /// 容器类型 docker podman
        /// </summary>
        private string _tool { get; set; } = "docker";

        /// <summary>
        /// 
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="input"></param>
        /// <param name="configtoken"></param>
        public LinkItem(ILogger<LinkHelper> logger, LinuxInfo input, string configtoken)
        {
            //Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(input));

            LinuxId = input.Id;
            sshaddress = input.SSHAddress;
            sshport = input.SSHPort;
            sshuser = input.SSHUser;
            sshpass = input.SSHPass;
            certbody = input.SSHCertBody;
            certpass = input.SSHCertPass;
            if (!String.IsNullOrEmpty(input.SSHCertBody))
            {
                loginmodel = EnumLinkLoginModel.CertBody;
            }
            else
            {
                loginmodel = EnumLinkLoginModel.Account;
            }
            _logger = logger;
            _tool = input.Tool;

            slime = new SemaphoreSlim(1);
        }


        /// <summary>
        /// 获取一个可用的链接
        /// </summary>
        /// <returns></returns>
        private async Task<LinkSSHClient?> _takeonessh()
        {


            if (list == null)
            {
                list = new List<LinkSSHClient>();
            }
            try
            {
                //并发处理
                await slime.WaitAsync();
                foreach (var item in list)
                {
                    if (!item.working)
                    {
                        return item;
                    }
                }

                LinkSSHClient take;
                if (list.Count < _clientMaxCount)
                {
                    #region 创建连接
                    switch (loginmodel)
                    {
                        case EnumLinkLoginModel.Account:
                            {
                                take = new LinkSSHClient(sshaddress, sshport, sshuser, sshpass);
                            }
                            break;
                        case EnumLinkLoginModel.CertBody:
                            {
                                using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
                                {
                                    System.IO.StreamWriter writer = new System.IO.StreamWriter(stream);
                                    writer.Write(certbody);
                                    writer.Flush();
                                    var n = new PrivateKeyFile[] { new PrivateKeyFile(stream, certpass) };
                                    take = new LinkSSHClient(sshaddress, sshport, sshuser, n);
                                }
                            }
                            break;
                        default:
                            {
                                if (OnEvented != null)
                                {
                                    OnEvented(LinuxId, "500", "等待启动更多链接超时！");
                                }
                                throw new PasteCodeException("wait for link timeout same work set time too much! 同时连接数超过最大值，连接失败！");
                            }
                    }
                    #endregion

                    #region 连接服务器，进行授权验证
                    if (take != null && take != default)
                    {
                        list.Add(take);
                        take.working = true;
                        return take;
                    }
                    else
                    {
                        throw new PasteCodeException("linux配置错误，无法建立有效的连接信息！");
                    }
                    #endregion
                }
                else
                {
                    //这里需要搞一个超时的机制，超出最大的抛出异常，表示服务不正常了。
                    var maxtime = 120;//这里是等待x秒进行排队？
                    while (maxtime > 0)
                    {
                        foreach (var item in list)
                        {
                            if (!item.working)
                            {
                                return item;
                            }
                        }
                        maxtime--;
                        await Task.Delay(1000);
                    }
                    if (maxtime <= 0)
                    {
                        throw new PasteCodeException("wait for link timeout same work set time too much!");
                    }
                    else
                    {
                        return null;
                    }
                }
            }
            catch (Exception exl)
            {
                _logger.LogException(exl, LogLevel.Error);
                throw;
            }
            finally
            {
                slime.Release();
            }
        }

        /// <summary>
        /// 执行单次命令
        /// </summary>
        /// <param name="commandstr"></param>
        /// <returns></returns>
        public async Task<(string msg, bool ok, int code)> RunSingleCommand(string commandstr)
        {
            var sshclient = await _takeonessh();
            if (sshclient != null)
            {
                if (!sshclient.IsConnected)
                {
                    sshclient.Connect();
                    if (sshclient.exception)
                    {
                        sshclient.exception = false;
                    }
                }
                //docker还是podman的适配
                if (!String.IsNullOrEmpty(_tool))
                {
                    if (_tool != "docker")
                    {
                        if (commandstr.StartsWith("docker"))
                        {
                            commandstr = $"{_tool}{commandstr.Substring(6)}";
                        }
                    }
                }
                //非root账号做sudo处理
                using var cmd = sshclient.CreateCommand((sshuser == "root" ? commandstr : $"sudo {commandstr}"));

                //超时时间10分钟
                cmd.CommandTimeout = TimeSpan.FromSeconds(600);
                var response = cmd.Execute();
                var error = cmd.Error;
                var code = cmd.ExitStatus.Value;

                cmd.Dispose();

                sshclient.working = false;

                if (!String.IsNullOrEmpty(error) && code != 0)
                {
                    _logger.LogError($"COMMAND:{commandstr} RESULT CODE:{code} ERROR:{error} RESPONSE:{response}");
                    return (error, false, code);
                }
                else
                {
                    if (!String.IsNullOrEmpty(error))
                    {
                        _logger.LogWarning($"COMMAND:{commandstr} RESULT CODE:{code} ERROR:{error} RESPONSE:{response}");
                    }
                    return (response, true, code);
                }
            }
            else
            {
                return ("没有获取到有效的链接信息！", false, 500);
            }
        }


        /// <summary>
        /// 不推荐  复制一个文件 需要远程检查下文件是否存在 
        /// stat -f filename
        /// </summary>
        /// <param name="filefrom"></param>
        /// <param name="fileto"></param>
        /// <returns></returns>
        public async Task<(bool ok, string response)> RemoteCopyFileTo(string filefrom, string fileto)
        {
            //如果用证书的，这里就错误了，需要重构下代码
            var returnResponse = String.Empty;
            System.Diagnostics.ProcessStartInfo pinfo = null;

            //这里需要判定，是否启用sudo的文件模式

            if (loginmodel == EnumLinkLoginModel.Account)
            {
                pinfo = new System.Diagnostics.ProcessStartInfo("config/copy.sh") { RedirectStandardOutput = true, CreateNoWindow = false, Arguments = $"{sshport} {sshuser} {sshaddress} {sshpass} {filefrom} {fileto}" };
            }
            else if (loginmodel == EnumLinkLoginModel.CertFile)
            {
                //这里的路径是 当前container的
                pinfo = new System.Diagnostics.ProcessStartInfo("config/certcopy.sh") { RedirectStandardOutput = true, CreateNoWindow = false, Arguments = $"{certpath} {sshport} {filefrom} {fileto} {sshuser} {sshaddress} {certpass}" };
            }
            else
            {
                if (String.IsNullOrEmpty(certpath))
                {
                    //写入一个临时目录中
                    var tempdir = "/app/temp/cert/";
                    if (!System.IO.Directory.Exists(tempdir))
                    {
                        Directory.CreateDirectory(tempdir);
                    }
                    var file = $"{tempdir}{sshaddress}.{sshport}.crt";
                    if (System.IO.File.Exists(file)) { System.IO.File.Delete(file); }
                    var writer = new StreamWriter($"{file}", false, new UTF8Encoding(false));
                    writer.WriteLine(certbody);
                    writer.Dispose();
                    certpath = file;
                }
                pinfo = new System.Diagnostics.ProcessStartInfo("config/certcopy.sh") { RedirectStandardOutput = true, CreateNoWindow = false, Arguments = $"{certpath} {sshport} {filefrom} {fileto} {sshuser} {sshaddress} {certpass}" };
            }
            if (System.IO.File.Exists(pinfo.FileName))
            {

                var proc = System.Diagnostics.Process.Start(pinfo);
                if (proc != null)
                {

                    //开始读取
                    using (var sr = proc.StandardOutput)
                    {
                        returnResponse = await sr.ReadToEndAsync();
                    }
                    proc.StandardOutput.Dispose();
                    proc.WaitForExit();

                    proc.Close();
                    proc.Dispose();

                    _logger.LogError($"scp file from:{filefrom} to:{fileto} success!");
                    //调用检车命令，确认文件是否存在
                    var check = await RunSingleCommand($"stat -f {fileto}");

                    return (check.ok, check.msg);
                }
                else
                {
                    _logger.LogError($"process start failed! can not start scp file {filefrom} to {fileto}");
                    return (false, $"process start failed! can not start scp file {filefrom} to {fileto}");
                }
            }
            else
            {
                _logger.LogError($"{pinfo.FileName} file not exits! can not start scp file {filefrom} to {fileto}");
                return (false, $"{pinfo.FileName} file not exits! can not start scp file {filefrom} to {fileto}");

            }

            //return (true, returnResponse);

        }


        /// <summary>
        /// 读取某一个文件
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public async Task<(bool, string)> ReadFile(string file)
        {
            var result = await RunSingleCommand($"cat {file}");
            return (result.ok, result.msg);
        }

        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            if (list != null)
            {
                foreach (var item in list)
                {
                    if (item.IsConnected)
                    {
                        item.Dispose();
                    }
                }
            }
        }
    }
}
